﻿namespace EFS.RC.CC.Channels.Contexts.Operators
{
	/// <summary>
	/// Operator context channel
	/// </summary>
	internal class Operator : EFS.Common.Async.AsyncRoot
	{
		#region Composition

		// async
		private object syncAccess = new object();

		// existing conversation
		private Microsoft.Rtc.Collaboration.Conversation conversation = null;

		// remote end point
		private Microsoft.Rtc.Collaboration.ParticipantEndpoint remoteEndPoint = null;

		// collaboration channel
		private Microsoft.Rtc.Collaboration.ConversationContextChannel conversationContextChannel = null;

		// initial context
		private System.String initialContextData = null;

		// Remote conversation 
		private System.String remoteConversationId = null;

		/// <summary>
		/// Status
		/// </summary>
		public enum OperatorContextChannelStatus { Idle, Started, Terminated }

		// Status
		private OperatorContextChannelStatus status = OperatorContextChannelStatus.Idle;

		#endregion

		#region Delegates

		// Content message received delegate
		public delegate void ContextMessageReceived(EFS.RC.CC.Messages.Message message);

		// Content message received delegate registrar 
		private ContextMessageReceived onContextMessageReceived;

		// Context channel established delegate
		public delegate void Established(EFS.RC.CC.Channels.Contexts.Operators.Operator operatorContextChannel);

		// Context channel established delegate registrar
		private Established onEstablished;

		#endregion

		#region Constructor

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="conversation"></param>
		/// <param name="remoteEndpoint"></param>
		public Operator(
			System.String initialContextData,
			Microsoft.Rtc.Collaboration.Conversation conversation,
			Microsoft.Rtc.Collaboration.ParticipantEndpoint remoteEndPoint,
			EFS.RC.CC.Channels.Contexts.Operators.Operator.Established onEstablished,
			EFS.RC.CC.Channels.Contexts.Operators.Operator.ContextMessageReceived onContextMessageReceived,
			System.String remoteConversationId = null)
		{
			// check
			EFS.Common.Verify.Reference(conversation, "conversation");
			EFS.Common.Verify.Reference(remoteEndPoint, "remoteEndPoint");
			EFS.Common.Verify.Reference(onEstablished, "onEstablished");
			EFS.Common.Verify.Reference(onContextMessageReceived, "onContextMessageReceived");

			// sets
			this.initialContextData = initialContextData;
			this.conversation = conversation;
			this.remoteEndPoint = remoteEndPoint;
			this.onContextMessageReceived = onContextMessageReceived;
			this.onEstablished = onEstablished;
			this.remoteConversationId = remoteConversationId;

			// log
			// commented for performance ~ EFS.EventLog.Log.LogInfo("SupervisorContext", this);
		}

		#endregion

		#region Usage

		/// <summary>
		/// Describe this object into a nice formated string
		/// </summary>
		/// <returns></returns>
		public override System.String Describe()
		{
			return System.String.Format("EFS.RC.CC.Channels.Contexts.Operators.Operator {0} {1}", conversation.Endpoint.OwnerUri, remoteEndPoint.Uri);
		}

		/// <summary>
		/// Begin startup
		/// </summary>
		public EFS.Common.Async.AsyncProcess BeginStartup()
		{
			// log
			// commented for performance ~ EFS.EventLog.Log.LogInfo("BeginStartup", this);

			// start
			return BeginAsyncProcess2(Startup, OnStartupComplete);
		}

		/// <summary>
		/// Send data message into the channel
		/// </summary>
		/// <param name="message"></param>
		/// <returns></returns>
		public System.IAsyncResult BeginDataSend(EFS.RC.CC.Messages.Message message)
		{
			// check
			EFS.Common.Verify.Reference(message, "message");

			// lock
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check
				if (null == conversationContextChannel || conversationContextChannel.State != Microsoft.Rtc.Collaboration.ConversationContextChannelState.Established)
				{
					throw new System.InvalidOperationException("Conversation Context Channel not available");
				}

				// begin send context data
				return conversationContextChannel.BeginSendData(
					new System.Net.Mime.ContentType(EFS.RC.CC.Messages.Message.ContentType),
					EFS.RC.CC.Messages.Message.SerializeToByteArray(message),
					EFS.Common.Async.AsyncResult.StateDelegate.End,
					EFS.Common.Async.AsyncResult.StateDelegate.State(EndSendContextData));
			}
		}

		/// <summary>
		/// Begin startup
		/// </summary>
		public EFS.Common.Async.AsyncProcess BeginShutdown()
		{
			// log
			// commented for performance ~ EFS.EventLog.Log.LogInfo("BeginStartup", this);

			// start
			return BeginAsyncProcess2(Shutdown, OnShutdownComplete);
		}

		#endregion

		#region Startup

		/// <summary>
		/// Startup
		/// </summary>
		private void Startup()
		{
			// log
			// commented for performance ~ EFS.EventLog.Log.LogInfo("Startup", this);

			// lock access
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				if (status != OperatorContextChannelStatus.Idle)
				{
					throw new System.InvalidOperationException("Only Idle SupervisorCentral can be started");
				}

				// started
				status = OperatorContextChannelStatus.Started;
			}

			// establish
			BeginAsyncProcess2(EstablishContextChannel);
		}

		/// <summary>
		/// Establish
		/// </summary>
		private void EstablishContextChannel()
		{
			// log
			// commented for performance ~ EFS.EventLog.Log.LogInfo("EstablishContextChannel", this);

			// lock access
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check status
				if (status != OperatorContextChannelStatus.Started)
				{
					throw new System.InvalidOperationException("Invalid Status for current operation. Expected Status: Started");
				}

				// options
				Microsoft.Rtc.Collaboration.ConversationContextChannelEstablishOptions conversationContextChannelEstablishOptions =
					new Microsoft.Rtc.Collaboration.ConversationContextChannelEstablishOptions();

				// make new
				conversationContextChannel = new Microsoft.Rtc.Collaboration.ConversationContextChannel(conversation, remoteEndPoint);
				conversationContextChannel.DataReceived += OnConversationContextDataReceived;
				conversationContextChannel.StateChanged += OnConversationContextChannelStateChanged;

				// options
				conversationContextChannelEstablishOptions.Toast = "operator-channel";
				conversationContextChannelEstablishOptions.ContextualData = initialContextData;
				conversationContextChannelEstablishOptions.RemoteConversationId = remoteConversationId;

				// begin
				conversationContextChannel.BeginEstablish(
					EFS.RC.CC.Config.Settings.CentralApplicationId,
					conversationContextChannelEstablishOptions,
					EFS.Common.Async.AsyncResult.StateDelegate.End,
					EFS.Common.Async.AsyncResult.StateDelegate.State(EndEstablisContextChannel));
			}
		}

		/// <summary>
		/// Start-up complete
		/// </summary>
		/// <param name="ar"></param>
		private void OnStartupComplete(EFS.Common.Async.AsyncProcess ap)
		{
			// log
			// commented for performance ~ EFS.EventLog.Log.LogInfo("OnStartupComplete", this);

			try
			{
				// check
				ap.Throw();

				// log
				// commented for performance ~ EFS.EventLog.Log.LogInfo("OnStartupComplete SUCCCESS", this);
			}
			catch (System.Exception ex)
			{
				// log
				EFS.EventLog.Log.LogException(ex, this);

				// close
				BeginShutdown();
			}
		}

		/// <summary>
		/// End establish
		/// </summary>
		/// <param name="result"></param>
		public void EndEstablisContextChannel(System.IAsyncResult ar)
		{
			// commented for performance ~ EFS.EventLog.Log.LogInfo("EndEstablisContextChannel", this);

			try
			{
				// try 
				conversationContextChannel.EndEstablish(ar);

				// check delegates
				onEstablished(this);
			}
			catch (System.Exception ex)
			{
				EFS.EventLog.Log.LogException(ex, this);

				// shut it down
				BeginShutdown();
			}
		}

		#endregion

		#region Runtime

		/// <summary>
		/// Unhandled exception
		/// </summary>
		/// <param name="ex"></param>
		protected override void OnException(System.Exception ex)
		{
			// log
			// commented for performance ~ EFS.EventLog.Log.LogInfo("OnException", this);

			// check
			EFS.Common.Verify.Reference(ex, "ex");

			// log
			EFS.EventLog.Log.LogException(ex, this);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(null, "OnException", ex, this);

			// shut down
			BeginShutdown();
		}

		/// <summary>
		/// State changed
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void OnConversationContextChannelStateChanged(object sender, Microsoft.Rtc.Collaboration.ConversationContextChannelStateChangedEventArgs ea)
		{
			// log
			// commented for performance ~ EFS.EventLog.Log.LogInfo("OnConversationContextChannelStateChanged", this);

			try
			{
				// terminated
				if (ea.State == Microsoft.Rtc.Collaboration.ConversationContextChannelState.Terminating ||
					ea.State == Microsoft.Rtc.Collaboration.ConversationContextChannelState.Terminated)
				{
					BeginShutdown();
				}
			}
			catch (System.Exception ex)
			{
				// handle ex
				HandleExceptionSafe(ex);
			}	
		}

		/// <summary>
		/// Data received on channel
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void OnConversationContextDataReceived(object sender, Microsoft.Rtc.Collaboration.ConversationContextChannelDataReceivedEventArgs ea)
		{
			// log
			// commented for performance ~ EFS.EventLog.Log.LogInfo("OnConversationContextDataReceived", this);

			try
			{
				if (EFS.Common.Verify.String(ea.ContentDescription.ContentType.MediaType))
				{
					// check content type
					if (EFS.RC.CC.Messages.Message.ContentType == ea.ContentDescription.ContentType.MediaType)
					{
						// send message
						EFS.RC.CC.Messages.Message message = EFS.RC.CC.Messages.Message.Deserialize(ea.RequestData.GetMessageBodyString());

						// check
						if (null != message)
						{
							onContextMessageReceived(message);
						}
					}
				}
			}
			catch (System.Exception ex)
			{
				// handle ex
				HandleExceptionSafe(ex);
			}
		}

		/// <summary>
		/// End data send
		/// </summary>
		/// <param name="result"></param>
		private void EndSendContextData(System.IAsyncResult result)
		{
			// log
			// commented for performance ~ EFS.EventLog.Log.LogInfo("EndSendContextData", this);

			try
			{
				// check
				conversationContextChannel.EndSendData(result);

				// log
				// commented for performance ~ EFS.EventLog.Log.LogInfo("EndSendContextData SUCCCESS", this);
			}
			catch (System.Exception ex)
			{
				// log
				EFS.EventLog.Log.LogException(ex, this);

				// close
				BeginShutdown();
			}
		}

		#endregion

		#region Shutdown

		/// <summary>
		/// Shut down
		/// </summary>
		public void Shutdown()
		{
			// commented for performance ~ EFS.EventLog.Log.LogInfo("Shutdown", this);

			// lock local acess
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check already terminated
				if (status != OperatorContextChannelStatus.Terminated)
				{
					// set terinated
					status = OperatorContextChannelStatus.Terminated;
				}
				else
				{
					// already terminated
					return;
				}
			}

			// make termination
			EFS.RC.CC.Tools.Functions.Termination termination = new EFS.RC.CC.Tools.Functions.Termination(this);

			// terminate all
			termination.Terminate(conversationContextChannel);
		}

		/// <summary>
		/// Shut down complete
		/// </summary>
		/// <param name="ar"></param>
		private void OnShutdownComplete(EFS.Common.Async.AsyncProcess ap)
		{
			// log
			// commented for performance ~ EFS.EventLog.Log.LogInfo("OnShutdownComplete", this);

			try
			{
				// check
				ap.Throw();

				// log
				// commented for performance ~ EFS.EventLog.Log.LogInfo("OnShutdownComplete SUCCCESS", this);
			}
			catch (System.Exception ex)
			{
				// log
				EFS.EventLog.Log.LogException(ex, this);
			}
		}

		#endregion
	}
}
